#include "cem/config_directory.h"

namespace cem
{

// 将诊断路径拼接一个键名
static inline std::vector<std::string> append_path(const std::vector<std::string> & base,
                                                   const std::string & key)
{
    auto p = base;
    p.push_back(key);
    return p;
}

std::optional<ConfigDirectory>
ConfigDirectory::subdir(const std::string & key, DiagnosticList * diags) const
{
    auto it = _object.props.find(key);
    if (it == _object.props.end())
    {
        if (diags)
            diags->push_back({DiagnosticSeverity::Error, "missing object key: " + key, append_path(_path, key)});
        return std::nullopt;
    }

    const std::shared_ptr<ConfigValue> & val_ptr = it->second;
    if (!val_ptr || !std::holds_alternative<std::shared_ptr<ConfigObject>>(val_ptr->value))
    {
        if (diags)
            diags->push_back({DiagnosticSeverity::Error, "key is not an object: " + key, append_path(_path, key)});
        return std::nullopt;
    }

    std::shared_ptr<ConfigObject> child_ptr = std::get<std::shared_ptr<ConfigObject>>(val_ptr->value);
    if (!child_ptr)
    {
        if (diags)
            diags->push_back({DiagnosticSeverity::Error, "object is null: " + key, append_path(_path, key)});
        return std::nullopt;
    }
    auto child_path = append_path(_path, key);
    return ConfigDirectory(key, *child_ptr, std::move(child_path));
}

std::optional<std::string>
ConfigDirectory::get_string(const std::string & key, DiagnosticList * diags) const
{
    auto it = _object.props.find(key);
    if (it == _object.props.end())
        return std::nullopt;
    const std::shared_ptr<ConfigValue> & val_ptr = it->second;
    if (!val_ptr || !std::holds_alternative<std::string>(val_ptr->value))
    {
        if (diags)
            diags->push_back({DiagnosticSeverity::Error, "key is not a string: " + key, append_path(_path, key)});
        return std::nullopt;
    }
    return std::get<std::string>(val_ptr->value);
}

std::optional<double>
ConfigDirectory::get_double(const std::string & key, DiagnosticList * diags) const
{
    auto it = _object.props.find(key);
    if (it == _object.props.end())
        return std::nullopt;
    const std::shared_ptr<ConfigValue> & val_ptr = it->second;
    if (!val_ptr)
        return std::nullopt;
    if (std::holds_alternative<double>(val_ptr->value))
        return std::get<double>(val_ptr->value);
    if (std::holds_alternative<int64_t>(val_ptr->value))
        return static_cast<double>(std::get<int64_t>(val_ptr->value));
    if (diags)
        diags->push_back({DiagnosticSeverity::Error, "key is not a number: " + key, append_path(_path, key)});
    return std::nullopt;
}

std::optional<int64_t>
ConfigDirectory::get_int64(const std::string & key, DiagnosticList * diags) const
{
    auto it = _object.props.find(key);
    if (it == _object.props.end())
        return std::nullopt;
    const std::shared_ptr<ConfigValue> & val_ptr = it->second;
    if (!val_ptr)
        return std::nullopt;
    if (std::holds_alternative<int64_t>(val_ptr->value))
        return std::get<int64_t>(val_ptr->value);
    if (diags)
        diags->push_back({DiagnosticSeverity::Error, "key is not an integer: " + key, append_path(_path, key)});
    return std::nullopt;
}

std::optional<bool>
ConfigDirectory::get_bool(const std::string & key, DiagnosticList * diags) const
{
    auto it = _object.props.find(key);
    if (it == _object.props.end())
        return std::nullopt;
    const std::shared_ptr<ConfigValue> & val_ptr = it->second;
    if (!val_ptr)
        return std::nullopt;
    if (std::holds_alternative<bool>(val_ptr->value))
        return std::get<bool>(val_ptr->value);
    if (diags)
        diags->push_back({DiagnosticSeverity::Error, "key is not a bool: " + key, append_path(_path, key)});
    return std::nullopt;
}

std::optional<ConfigArray>
ConfigDirectory::get_array(const std::string & key, DiagnosticList * diags) const
{
    auto it = _object.props.find(key);
    if (it == _object.props.end())
        return std::nullopt;
    const std::shared_ptr<ConfigValue> & val_ptr = it->second;
    if (!val_ptr || !std::holds_alternative<std::shared_ptr<ConfigArray>>(val_ptr->value))
    {
        if (diags)
            diags->push_back({DiagnosticSeverity::Error, "key is not an array: " + key, append_path(_path, key)});
        return std::nullopt;
    }
    auto arr_ptr = std::get<std::shared_ptr<ConfigArray>>(val_ptr->value);
    if (!arr_ptr)
        return std::nullopt;
    return *arr_ptr; // 返回拷贝
}

} // namespace cem